package sky.ikaros.camera;

import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.ReentrantLock;

@SuppressWarnings("deprecation")
public abstract class SimpleCameraManager extends Thread  implements SurfaceHolder.Callback, SurfaceTexture.OnFrameAvailableListener{
    private static final String TAG = "CameraManager";
    private static final String THREAD_NAME = "CameraThread";
    private boolean interrupt;
    private final ArrayBlockingQueue<byte[]> queue = new ArrayBlockingQueue<>(1);
    private int width, height, cameraId = -1;
    private Camera camera;
    private boolean isOpened, isReady;
    private final ReentrantLock lock = new ReentrantLock();
    public SimpleCameraManager(){
        super(THREAD_NAME);
        start();
    }

    /// 打开相机
    public void open(int cameraId){
        createCamera(cameraId);
    }
    private void createCamera(int cameraId){
        if (camera != null || isOpened) return;
        while(!isReady);
        lock.lock();
        this.cameraId = cameraId;
        int cameraNum = Camera.getNumberOfCameras();
        Log.d(TAG, "===> the camera count is: " + cameraNum);
        if (cameraNum <= 0) {
            onCameraError(new IllegalStateException("camera number is smaller than 0"));
            lock.unlock();
            return;
        }
        Log.d(TAG, "===> Using camera index is: " + cameraId);
        if (cameraId == -1) {
            onCameraError(new IllegalStateException("using camera index exception"));
            lock.unlock();
            return;
        }
        camera = cameraNum > 1 ? Camera.open(cameraId) : Camera.open();
        if (camera == null) {
            onCameraError(new IllegalStateException("camera opened exception"));
            lock.unlock();
            return;
        }
        Size s = getRequestSize();
        boolean status = setCameraParameter(getImageFormat(), s.getWidth(), s.getHeight());
        lock.unlock();
        if(!status) return;
        isOpened = true;
        CameraRenderType renderType = getRenderType();
        if (renderType == CameraRenderType.FOREGROUND) setForegroundPreview();
        else if (renderType == CameraRenderType.BACKGROUND) setBackgroundPreview();
    }
    private void setForegroundPreview(){
        final SurfaceView sv = getForegroundRenderer();
        if (sv == null) return;
        sv.post(() -> {
            SurfaceHolder holder = sv.getHolder();
            if (holder == null || holder.getSurface() == null) {
                Log.w(TAG, "===> the holder is null or the holder surface is null");
                return;
            }
            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            holder.addCallback(this);
            Log.d(TAG, "===> Camera foreground preview frame set ok");
            Log.d(TAG, "===> surface created: " + holder.isCreating());
            if (!holder.isCreating()) surfaceCreated(holder);
        });
    }
    private EGLContext eglContext;
    private EGLDisplay eglDisplay;
    private EGLSurface eglSurface;
    private EGLConfig eglConfig;
    private boolean createEGL(){
        lock.lock();
        if(eglDisplay == null) {
            eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
            if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
                Log.e(TAG, "===> create EGLDisplay fail");
                lock.unlock();
                return false;
            }
            int[] version = new int[2];
            if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
                Log.e(TAG, "===> initialize EGL fail");
                lock.unlock();
                return false;
            }
        }
        if(eglConfig == null){
            int[] configAttributes = {
                    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                    EGL14.EGL_RED_SIZE, 8,
                    EGL14.EGL_GREEN_SIZE, 8,
                    EGL14.EGL_BLUE_SIZE, 8,
                    EGL14.EGL_ALPHA_SIZE, 8,
                    EGL14.EGL_DEPTH_SIZE, 16,
                    EGL14.EGL_STENCIL_SIZE, 8,
                    EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
                    EGL14.EGL_NONE
            };
            EGLConfig[] configs = new EGLConfig[1];
            int[] numConfigs = new int[1];
            if(!EGL14.eglChooseConfig(eglDisplay, configAttributes, 0, configs, 0,1, numConfigs, 0)){
                Log.e(TAG, "===> choose EGLConfig fail");
                lock.unlock();
                return false;
            }
            if (numConfigs[0] == 0) {
                Log.e(TAG, "===> EGLConfig choose Error");
                lock.unlock();
                return false;
            }
            eglConfig = configs[0];
        }
        if(eglSurface == null) {
            int[] surfaceAttributes = {EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE};
            eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttributes, 0);
        }
        if (eglSurface == EGL14.EGL_NO_SURFACE) {
            Log.e(TAG, "===> create EGLSurface fail");
            lock.unlock();
            return false;
        }
        if(eglContext == null) {
            int[] contextAttributes = {
                    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                    EGL14.EGL_NONE
            };
            eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttributes, 0);
            if (eglContext == EGL14.EGL_NO_CONTEXT){
                Log.e(TAG, "===> create EGLContext fail");
                lock.unlock();
                return false;
            }
        }
        if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
            Log.e(TAG, "===> bind EGLContext fail");
            lock.unlock();
            return false;
        }
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        Log.d(TAG, "===> init EGL ok");
        lock.unlock();
        return true;
    }

    private SurfaceTexture surfaceTexture;
    private void createSurfaceTexture() {
        if(surfaceTexture != null) return;
        lock.lock();
        int textureId = createExternalTexture();
        surfaceTexture = new SurfaceTexture(textureId);
        surfaceTexture.setOnFrameAvailableListener(this);
        lock.unlock();
    }
    private int createExternalTexture() {
        int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        return textures[0];
    }
    private void setBackgroundPreview(){
        if(camera == null) return;
        try {
            camera.setPreviewTexture(surfaceTexture);
            startPreview();
            setPreviewCallbackWithBuffer();
        } catch (IOException e) {
            onCameraError(e);
        }
    }
    private void closeSurfaceTexture(){
        if (surfaceTexture == null) return;
        lock.lock();
        surfaceTexture.release();
        surfaceTexture = null;
        lock.unlock();
    }
    private void closeEGL(){
        lock.lock();
        if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
            EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
            if (eglContext != EGL14.EGL_NO_CONTEXT) {
                EGL14.eglDestroyContext(eglDisplay, eglContext);
            }
            if (eglSurface != EGL14.EGL_NO_SURFACE) {
                EGL14.eglDestroySurface(eglDisplay, eglSurface);
            }
            EGL14.eglTerminate(eglDisplay);
        }
        eglDisplay = null;
        eglContext = null;
        eglSurface = null;
        eglConfig = null;
        lock.unlock();
    }
    private boolean setCameraParameter(int previewFormat, int requestWidth, int requestHeight) {
        try {
            Camera.Parameters parameters = camera.getParameters();
            Size size = getSupportSize(parameters, requestWidth, requestHeight);
            width = size.getWidth();
            height = size.getHeight();
            parameters.setPreviewSize(width, height);
            Log.d(TAG, String.format("===> the Camera preview size that setting is width=%d, height=%d", width, height));
            parameters.setPreviewFormat(previewFormat);
            Log.d(TAG, "===> Camera preview formatter is " + previewFormat);
            parameters = getCameraPreviewConfig(parameters);
            camera.setParameters(parameters);
            Log.d(TAG, "===> Camera parameters set ok");
            int nativeOrientation = getNativeOrientation();
            Log.d(TAG, "===> Camera native orientation is " + nativeOrientation);
            int rotation = getDisplayOrientation();
            Log.d(TAG, "===> Camera preview orientation is " + nativeOrientation);
            camera.setDisplayOrientation(rotation);
            return true;
        } catch (Exception e) {
            onCameraError(e);
            return false;
        }
    }
    private void setPreviewCallbackWithBuffer(){
        camera.setPreviewCallbackWithBuffer((data, camera1) -> {
            queue.offer(data);
            camera1.addCallbackBuffer(data);
        });
    }
    private void closeCamera(){
        if(camera == null) return;
        lock.lock();
        camera.setPreviewCallbackWithBuffer(null);
        camera.release();
        camera = null;
        isOpened = false;
        lock.unlock();
    }
    /// 自定义选取尺寸
    protected Size getSupportSize(Camera.Parameters parameters, int width, int height) {
        List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
        StringBuilder sb = new StringBuilder("[");
        for (Camera.Size size : sizes) {
            sb.append(String.format(Locale.CHINA, "[%d, %d], ", size.width, size.height));
        }
        sb.append("]");
        Log.d(TAG, "getSupportSize is: " + sb.toString());
        final int s = width * height;
        Camera.Size result = sizes.get(sizes.size() - 1);
        for (int i = sizes.size() - 1; i >= 1; i--) {
            Camera.Size size = sizes.get(i);
            if (size.width * size.height >= s) {
                Camera.Size size2 = sizes.get(i - 1);
                if (size2.width * size2.height <= s) {
                    result = size;
                }
            }
        }
        return new Size(result.width, result.height);
    }
    /// 获取渲染的类型，默认为前台渲染
    protected CameraRenderType getRenderType() {
        return CameraRenderType.FOREGROUND;
    }
    /// 获取前台渲染的节点
    protected SurfaceView getForegroundRenderer() {
        return null;
    }
    /// 设置相机预览配置
    protected Camera.Parameters getCameraPreviewConfig(Camera.Parameters parameters) {
        return parameters;
    }
    /// 获取相机
    public Camera getCamera() {
        return camera;
    }
    /// 获取是前置相机还是后置
    public int getFacing() {
        if(cameraId == -1) return -1;
        try {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId, info);
            return info.facing;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
            return -1;
        }
    }
    /// 获取相机的原生角度
    public int getNativeOrientation() {
        if(cameraId == -1) return 0;
        try {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId, info);
            return info.orientation;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
            return 0;
        }
    }
    /// 获取预览的角度
    public int getDisplayOrientation() {
        return 0;
    }
    /// 获取相机的尺寸
    public Size getSize() {
        return new Size(width, height);
    }
    /// 获取相机打开状态
    public boolean getOpenState() {
        return isOpened;
    }
    /// 获取照片格式，默认为NV21
    public int getImageFormat() {
        return ImageFormat.NV21;
    }
    /// 关闭相机
    public void close() {
        closeCamera();
    }
    /// 释放资源
    public void release(){
        close();
        interrupt();
    }
    /// 重新打开相机
    public void reopen() {
        if(cameraId == -1) return;
        close();
        open(cameraId);
    }
    /// 开启预览
    public void startPreview() {
        if (camera == null) return;
        lock.lock();
        camera.startPreview();
        Camera.Parameters parameters = camera.getParameters();
        PixelFormat pixelInfo = new PixelFormat();
        int pixelFormat = parameters.getPreviewFormat();
        PixelFormat.getPixelFormatInfo(pixelFormat, pixelInfo);
        int bufSize = width * height * pixelInfo.bitsPerPixel / 8;
        camera.addCallbackBuffer(new byte[bufSize]);
        Log.d(TAG, "===> Camera preview start");
        lock.unlock();
    }
    /// 结束预览
    public void stopPreview(){
        if (camera == null) return;
        lock.lock();
        camera.stopPreview();
        Log.d(TAG, "===> Camera preview stop");
        lock.unlock();
    }
    /// 将nv21数据转成图像bit
    public byte[] transformNV21Data2ImageData(byte[] nv21Data){
        return transformNV21Data2ImageData(nv21Data, 100);
    }
    public byte[] transformNV21Data2ImageData(byte[] nv21Data, int quality){
        YuvImage yuvImage = new YuvImage(nv21Data, ImageFormat.NV21, width, height, null);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        yuvImage.compressToJpeg(new Rect(0, 0, width, height), quality, out); // 压缩质量为 100%
        byte[] jpegData = out.toByteArray();
        try {
            out.close();
        } catch (IOException ignored) { }
        return jpegData;
    }

    @Override
    public void run() {
        if(getRenderType() == CameraRenderType.BACKGROUND){
            if(!createEGL()) return;
            createSurfaceTexture();
        }
        isReady = true;
        while(!interrupt){
            try {
                byte[] data = queue.take();
                if(data == null) continue;
                if (surfaceTexture != null) {
                    surfaceTexture.updateTexImage();
//                    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
                }
                onPreviewFrame(data);
            } catch (InterruptedException ignored) { }
        }
        closeSurfaceTexture();
        closeEGL();
        isReady = false;
    }

    @Override
    public void interrupt() {
        super.interrupt();
        interrupt = true;
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        Log.d(TAG, "===> Camera surface created");
        if (camera == null) return;
        try {
            camera.setPreviewDisplay(holder);
            startPreview();
            setPreviewCallbackWithBuffer();
        } catch (IOException e) {
            onCameraError(e);
        }
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        surfaceDestroyed(holder);
        setCameraParameter(format, width, height);
        surfaceCreated(holder);
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        Log.d(TAG, "===> Camera surface destroyed");
        stopPreview();
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
    }

    /// 相机出错后调用
    protected void onCameraError(Exception e) {
        Log.e(TAG, e.getMessage(), e);
    }
    /// 预览数据
    protected abstract void onPreviewFrame(byte[] data);

    /// 获取相机建议尺寸
    protected abstract Size getRequestSize();
}
